class TasksController < ApplicationController
  before_filter :login_required
  before_filter :editor_required, :only => [:periods, :new, :create, :update, :add_worker, :destroy]

  before_filter :set_list_params, :only => [:list, :calendar]

  def index
    action = session[:view] == 'list' ? 'list' : 'calendar'
    redirect_to :action => action
  end

  def list
    session[:view] = 'list'

    @teams = Team.find(:all)
    @members = find_all_members

    options = {
      :include => [{:periods, :user}]
    }
    if @view == :member || @member
      options[:conditions] = ['periods.begin_at < ? and periods.end_at >= ?',
                              @date.end_of_month,
                              @date.beginning_of_month]
    else
      options[:conditions] = ['tasks.begin_at < ? and tasks.end_at >= ?',
                              @date.end_of_month,
                              @date.beginning_of_month]
      options[:order] = "tasks.begin_at, tasks.end_at"
    end

    if @member
      options[:conditions][0] << ' and users.id = ?'
      options[:conditions] << @member.id
    elsif @team
      options[:conditions][0] << ' and users.team_id = ?'
      options[:conditions] << @team.id
    end
    @tasks = Task.find(:all, options);

    if @view == :member || @member
      @users = @tasks.inject({}) {|user_tasks, task|
        task.periods.each do |period|
          user_tasks[period.user] ||= {}
          user_tasks[period.user][task] ||= []
          user_tasks[period.user][task] << period
        end
        user_tasks
      }.to_a.sort {|a, b|
        a[0].display_order <=> b[0].display_order
      }.map {|user, periods|
        [user, periods.to_a.sort {|a, b| a.first.begin_at <=> b.first.begin_at }]
      }
    end
  end

  def calendar
    session[:view] = 'calendar'

    @teams = Team.find(:all)
    @members = find_all_members

    @week_tasks =
      if @member
        week_tasks_for_member
      else
        case @view
        when :all
          week_tasks_for_all
        when :member
          week_tasks_for_members
        end
      end
  end

  # GET /tasks/1
  # GET /tasks/1.xml
  def show
    @task = Task.find(params[:id])
    @members = find_all_members - @task.users.map {|user, periods| user }

    respond_to do |format|
      format.html # show.html.erb
      format.xml  { render :xml => @task }
    end
  end

  # GET /tasks/new
  # GET /tasks/new.xml
  def new
    @task = Task.new(params[:task])
    @members = find_all_members
    params[:workers] ||= current_user.name

    respond_to do |format|
      format.html # new.html.erb
      format.xml  { render :xml => @task }
    end
  end

  # GET /tasks/1/edit
  # def edit
  #   @task = Task.find(params[:id])
  # end

  # POST /tasks
  # POST /tasks.xml
  def create
    @task = Task.new(params[:task])
    @task.periods << Period.new(
      :user_name => params[:workers],
      :begin_at => @task.begin_at,
      :end_at => @task.end_at
    )
    if @task.save
      flash[:notice] = "タスク「#{@task.title}」を作成しました。"
      redirect_to @task
    else
      @members = find_all_members
      render :action => 'new'
    end
  end

  # PUT /tasks/1
  def update
    @task = Task.find(params[:id])
    last_begin_at = @task.begin_at
    last_end_at = @task.end_at

    begin
      Task.transaction do
        @task.update_attributes(params[:task]) or raise

        if @task.begin_at > last_begin_at
          # 前はみ出し削除
          Period.delete_all(["task_id = ? AND end_at < ?", @task.id, @task.begin_at])
          # 前切り詰め
          Period.update_all("begin_at = '#{@task.begin_at.strftime('%Y-%m-%d %T')}'",
                            ["task_id = ? AND begin_at < ?", @task.id, @task.begin_at])
        elsif @task.begin_at < last_begin_at
          # 前伸張
          Period.update_all("begin_at = '#{@task.begin_at.strftime('%Y-%m-%d %T')}'",
                            ["task_id = ? AND begin_at = ?", @task.id, last_begin_at])
        end

        if @task.end_at < last_end_at
          # 後ろはみ出し削除
          Period.delete_all(["task_id = ? AND begin_at > ?", @task.id, @task.end_at])
          # 後ろ切り詰め
          Period.update_all("end_at = '#{@task.end_at.strftime('%Y-%m-%d %T')}'",
                            ["task_id = ? AND end_at > ?", @task.id, @task.end_at])
        elsif @task.end_at > last_end_at
          # 後ろ伸張
          Period.update_all("end_at = '#{@task.end_at.strftime('%Y-%m-%d %T')}'",
                            ["task_id = ? AND end_at = ?", @task.id, last_end_at])
        end
        
        unless @task.periods.count > 0
          @task.errors.add(:time, "期間内に担当者が含まれません。")
          raise
        end
      end
      flash[:notice] = '変更を保存しました。'
      redirect_to @task
    rescue
      @members = find_all_members - @task.users.map {|user, periods| user }
      @editting = true
      render :action => :show
    end
  end

  # POST /tasks/1/periods
  def periods
    @task = Task.find(params[:id])
    
    begin
      Period.transaction do
        Period.delete_all(:task_id => @task.id)

        count = 0

        task_begin_at = @task.end_at
        task_end_at = @task.begin_at

        params[:workers].each do |user_id, periods|
          user = User.find(user_id)
          periods.split(';').each do |dates|
            begin_at, end_at = dates.split(',').map {|date| Time.parse(date) }
            Period.create!(
              :task_id => @task.id,
              :user_id => user.id,
              :begin_at => begin_at,
              :end_at => end_at
            )
            count += 1
            task_begin_at = [task_begin_at, begin_at].min
            task_end_at = [task_end_at, end_at].max
          end
        end
        if count == 0
          flash[:error] ||= "担当者は1人以上必要です。"
          raise
        end
        @task.update_attributes(
          :begin_at => task_begin_at,
          :end_at => task_end_at
        ) or raise
      end
      flash[:notice] ||= "担当者の変更を保存しました。"
    rescue
      flash[:error] ||= "変更の保存に失敗しました。"
    end

    redirect_to @task
  end

  #POST /tasks/1/add_worker
  def add_worker
    @task = Task.find(params[:id])

    begin
      user = User.find_by_name(params[:name])
      unless user
        flash[:error] = "#{params[:name]} は存在しません。"
        raise
      end

      if @task.periods.find(:first, :conditions => { :user_id => user.id })
        flash[:error] = "#{user.name} はすでに担当者です。"
        raise
      end

      @task.periods << Period.new(
        :user_id => user.id,
        :begin_at => @task.begin_at,
        :end_at => @task.end_at
      )
      @task.save!

      flash[:notice] = "#{user.name} を担当者に追加しました。"
    rescue
      flash[:error] ||= "担当者の追加に失敗しました。"
    end

    redirect_to @task
  end

  # DELETE /tasks/1
  # DELETE /tasks/1.xml
  def destroy
    @task = Task.find(params[:id])
    @task.destroy

    respond_to do |format|
      format.html { redirect_to(tasks_url) }
      format.xml  { head :ok }
    end
  end


  protected

  def set_list_params
    set_team && set_member && set_view && set_date
  end

  def set_team
    params[:team] ||= session[:team]
    if params[:team] == 'all'
      # all
    elsif params[:team].blank?
      if current_user.has_team?
        @team = current_user.team
        params[:team] = @team.id.to_s
      end
    else
      begin
        @team = Team.find(params[:team])
      rescue ActiveRecord::RecordNotFound
        redirect_to params.merge(:team => 'all')
        return false
      end
    end
    session[:team] = params[:team]
    true
  end

  def set_member
    params[:member] ||= session[:member]
    if params[:member] == 'all' || params[:member].blank?
      params[:member] = 'all'
    else
      begin
        @member = User.find(params[:member])
        params[:member] = @member.id.to_s
      rescue ActiveRecord::RecordNotFound
        redirect_to params.merge(:member => 'all')
        return false
      end
    end
    session[:member] = params[:member]
    true
  end

  def set_view
    params[:view] ||= session[:view]
    @view = params[:view] == 'all' ? :all : :member
    session[:view] = params[:view] = @view.to_s
  end

  def set_date
    params[:date] ||= session[:date]
    @today = Time.now.beginning_of_day
    if !params[:date].blank?
      if match = params[:date].match(/^(\d{4})(\d{2})$/)
        @date = Time.local(match[1].to_i, match[2].to_i)
      else
        redirect_to :date => nil
        return false
      end
    end
    @date ||= @today
    session[:date] = params[:date] = @date.strftime('%Y%m')
    true
  end


  private

  def find_all_members
    if @team
      User.find(:all, :conditions => { :team_id => @team.id }, :order => :display_order)
    else
      User.find(:all, :order => :display_order)
    end
  end

  def week_tasks_for_all
    options = {
      :conditions => ['tasks.begin_at < ? and tasks.end_at >= ?',
                      @date.end_of_month.next_week,
                      @date.beginning_of_month.beginning_of_week],
      :order => 'tasks.begin_at'
    }
    if @team
      options[:include] = [{:periods, :user}]
      options[:conditions][0] += ' and users.team_id = ?'
      options[:conditions] << @team.id
    end
    all_tasks = Task.find(:all, options)

    week_tasks = []
    each_week_tasks(all_tasks) do |first_day, task_rows|
      week_tasks << {
        :first_day => first_day,
        :task_rows => task_rows
      }
    end
    week_tasks
  end

  def week_tasks_for_members
    options = {
      :include => [{:periods, :task}],
      :conditions => ['periods.begin_at < ? and periods.end_at >= ?',
                      @date.end_of_month.next_week,
                      @date.beginning_of_month.beginning_of_week],
      :order => 'users.display_order, periods.begin_at'
    }
    if @member
      options[:conditions][0] += ' and users.id = ?'
      options[:conditions] << @member.id
    elsif @team
      options[:conditions][0] += ' and users.team_id = ?'
      options[:conditions] << @team.id
    end
    members = User.find(:all, options)
    
    week_tasks = []

    if members.empty?
      each_week_tasks([]) do |first_day, task_rows|
        week_tasks << { :first_day => first_day }
      end
    else
      members.each do |member|
        week_count = 0
        each_week_tasks(member.periods) do |first_day, task_rows|
          week = week_tasks[week_count] ||= {
            :first_day => first_day,
            :member_rows => [],
          }
          unless task_rows.empty?
            week[:member_rows] << {
              :member => member,
              :task_rows => task_rows,
            }
          end
          week_count += 1
        end
      end
    end
    week_tasks
  end

  def week_tasks_for_member
    week_tasks_for_members.map do |week|
      {
        :first_day => week[:first_day],
        :task_rows => (week[:member_rows] && week[:member_rows].first && week[:member_rows].first[:task_rows]) || []
      }
    end
  end

  def each_week_tasks(all_tasks, &block)
    tasks = []
    day = @date.beginning_of_month.beginning_of_week
    last = @date.end_of_month
    while day <= last
      next_week = day.next_week
      tasks = tasks.select {|task|
        task.end_at >= day
      }.concat(all_tasks.shift_while {|task| task.begin_at < next_week })
      block.call(day, split_tasks_to_row(tasks))
      day = next_week
    end
  end

  def split_tasks_to_row(tasks)
    tasks = tasks.dup
    result = []
    row = []
    while !tasks.empty?
      latest = tasks.shift
      row = [latest]
      rests = []
      tasks.each do |task|
        if latest.end_at < task.begin_at
          row << task
          latest = task
        else
          rests << task
        end
      end
      result << row
      tasks = rests
    end
    result
  end

  def split_tasks_to_members(tasks)
    member_tasks = {}
    tasks.each do |task|
      task.periods.each do |period|
        (member_tasks[period.user] ||= []) << task
      end
    end
    member_tasks.keys.sort {|a, b| a.id <=> b.id }.map do |user|
      {
        :member => user,
        :task_rows => split_tasks_to_row(member_tasks[user])
      }
    end
          
  end

end
